﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Noris.Tools.TraceVisualiser.Interface;
using Noris.Tools.TraceVisualiser.Support;
using Noris.Tools.TraceVisualiser.SqlParser;
using Noris.Tools.TraceVisualiser.Forms;
using Noris.Tools.TraceVisualiser.Green.AplServer;
using Noris.Tools.TraceVisualiser.TraceData;
using System.Windows.Forms;
using System.Drawing;

namespace Noris.Tools.TraceVisualiser.Green
{
    #region Zobrazení SQL příkazů jako Text
    public class Func_SqlShowText : Func_SqlBase, IFunctionContextMenuItem
    {
		#region IPlugin Members
		/// <summary>
		/// Příznak, že Plugin je aktivní ve standardním běhu (tj. při běžném užiavtelském spuštění).
		/// Plně funkční pluginy tedy vrací true.
		/// Pluginy, které jsou ve vývoji, vrací false, takové jsou zobrazeny / spouštěny pouze v aplikaci, která běží v režimu Debug.
		/// </summary>
		bool IPlugin.Active { get { return true; } }
		#endregion
		#region IFunctionContextMenuItem Members
        bool IFunctionContextMenuItem.IsFunctionSuitableFor(FunctionContextMenuItemSuitableArgs args)
        {
            args.MenuItems.Add(new FunctionMenuItem(30, "Vypiš SQL příkazy jako Text",
				"Texty SQL příkazů zformátuje do jednoho spustitelného textu.",
				true, Noris.Tools.TraceVisualiser.Pics.PicLibrary16.Script_FromFile, null));
            return true;
        }
        bool IFunctionContextMenuItem.IsMenuItemEnabledFor(FunctionContextMenuItemRunArgs args)
        {
            List<ISqlEventData> items = this.GetInputEventsOfType<ISqlEventData>(args, 1);
            return (items != null && items.Count > 0);
        }
        void IFunctionContextMenuItem.Run(FunctionContextMenuItemRunArgs args)
        {
            this.RunShowSqlText(args);
        }
        #endregion
        #region Práce s SQL dotazy
        /// <summary>
        /// Načte a zobrazí SQL příkazy
        /// </summary>
        /// <param name="args"></param>
        protected void RunShowSqlText(FunctionContextMenuItemRunArgs args)
        {
            // Získat data, setřídit data:
            List<ISqlEventData> items = this.GetInputEventsOfType<ISqlEventData>(args);
            int cnt = items.Count;
            if (cnt == 0)
            {   // K tomu by nemělo dojít, protože pokud nejsou vybrané SQL záznamy, pak je položka menu Disabled.
                Dialog.ShowInfo("Mezi vybranými záznamy není žádný SQL příkaz.");
                return;
            }

            if (cnt > 1)
                items.Sort(CompareISqlEvent);

            string rtfText = _GetSqlText(items);
            if (rtfText == null)
                Dialog.ShowWarning("Text se nepodařilo sestavit.");
            else
            {
                // Formulář:
                Steward.OpenDialogFormInTryCatch(typeof(ShowTextForm), "V okně došlo k chybě: %0", delegate(Form form)
                {
                    ShowTextForm sForm = form as ShowTextForm;
                    sForm.FindPanelVisible = true;
                    sForm.ReadOnly = true;
                    if (this.CurrentCoderIsRtf)
                        sForm.CurrentRtfText = rtfText;
                    else
                        sForm.CurrentText = rtfText;
                });
            }
        }
        /// <summary>
        /// Vrací text SQL příkazů.
        /// Podle možností je ve formátu RTF, podle možností i včetně Syntax Colouring.
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        private string _GetSqlText(List<ISqlEventData> items)
        {
            string result;

            // Spočítáme celkovou dobu SQL příkazů:
            double totalTime = items.Sum(data => data.Time.Seconds);

            // Může nastat situace, kdy uživatel vybral příliš mnoho elementů. Pak by syntax-coloring trval extrémě dlouho.
            // Řešení? Necháme připravovat Syntax-coloring po nějakou dobu (6 sekund).
            // Pokud by to trvalo déle, pak příprava abortuje a zobrazí se plain-text:
            result = _GetSqlTextInMode(items, totalTime, CoderMode.RtfSyntaxColoring, 3d);
            if (result == null)
                result = _GetSqlTextInMode(items, totalTime, CoderMode.RtfPlain, 3d);
            if (result == null)
                result = _GetSqlTextInMode(items, totalTime, CoderMode.OnlyText, null);

            return result;
        }
        /// <summary>
        /// Pokusí se sestavit a vrátit syntax-coloured text SQL příkazů.
        /// Má na to stanovený čas.
        /// Pokud to nestihne v daném čase, vrátí null.
        /// </summary>
        /// <param name="items"></param>
        /// <param name="timeOut"></param>
        /// <returns></returns>
        private string _GetSqlTextInMode(List<ISqlEventData> items, double totalTime, CoderMode currentCoderMode, double? timeOut)
        {
            this.CurrentCoderMode = currentCoderMode;

            bool isRtf = (currentCoderMode == CoderMode.RtfSyntaxColoring || currentCoderMode == CoderMode.RtfPlain);
            if (isRtf)
            {
                this.RtfText = new RtfCoder(true);
                this.RtfText.Add(RtfItem.NewProtect(true));
                this.TxtText = null;
            }
            else
            {
                this.RtfText = null;
                this.TxtText = new StringBuilder();
            }

            DateTime startTime = DateTime.Now;
            bool hasTimeOut = timeOut.HasValue && timeOut.Value > 0d;
            DateTime? endTime = (hasTimeOut ? (DateTime?)startTime.AddSeconds(timeOut.Value) : (DateTime?)null);

            int? pid = null;
            int cnt = items.Count;
            for (int i = 0; i < cnt; i++)
            {
                // Timeout?
                if (hasTimeOut)
                {   // Timeout je již dosažen? Skončíme:
                    DateTime now = DateTime.Now;
                    if (now > endTime.Value)
                        return null;
                    // Timeout se blíží (jsme za první třetinou času), a konec práce je v nedohlednu (pozice [i] je menší než 1/6) ?
                    double timeRatio = ((TimeSpan)(now - startTime)).TotalSeconds / timeOut.Value;
                    double currRatio = (double)i / (double)cnt;
                    if (timeRatio > 0.333d && currRatio < 0.175d)
                        return null;
                    if (timeRatio > 0.667d && currRatio < 0.425d)
                        return null;
                }

                // Tuhle položku ISqlEventData zpracujeme do RTF:
                ISqlEventData iSqlEvent = items[i];
                ISqlEventExtendedData iSqlExtend = iSqlEvent as ISqlEventExtendedData;
                bool isExtended = (iSqlExtend != null);

                if (!pid.HasValue || (pid.HasValue && iSqlEvent.SqlPId != pid.Value))
                {   // Začíná první / jiný PID:
                    if (pid.HasValue)
                        this._AddTitle("------------------ End of SQL PID: " + pid.Value.ToString());

                    pid = iSqlEvent.SqlPId;
                    this._AddTitle("------------------ Begin of SQL PID: " + iSqlEvent.SqlPId.ToString());

                }

                // Time, poměr času:
                string timeText = "-- Execution time=" + iSqlEvent.Time.StringFullDetail + ";";
                timeText = timeText.PadRight(80) + "   Time/Total=";
                double seconds = iSqlEvent.Time.Seconds;
                if (seconds <= 0d)
                    timeText += "   0%";
                else
                    timeText += Math.Round(100 * seconds / totalTime, 2).ToString("##0.00").PadLeft(7) + "%";
                this._AddComment(timeText);           // ElementTime
                
                if (isExtended)
                {
                    string lastStackLine = _GetLastSqlLine(iSqlExtend.StackTrace);
                    if (!String.IsNullOrEmpty(lastStackLine))
                        this._AddComment("-- Stack: ", lastStackLine);
                    this._AddComment("-- Sql parameters: ", iSqlExtend.SqlParameters);
                }
                this._AddCommentBlock(iSqlEvent.SqlComment);
                this._AddSqlText(iSqlEvent.SqlCommand);

                if (isExtended)
                {
                    this._AddComment("-- Result scalar: ", iSqlExtend.ResultScalar);
                    if (iSqlExtend.ResultRowCount > 0)
                        this._AddComment("-- Result row count: ", iSqlExtend.ResultRowCount.ToString());
                }

                this.RtfText.Add(RtfItem.NewLine);
            }
            if (pid.HasValue)
                this._AddTitle("------------------ End of SQL PID: " + pid.Value.ToString());

            // Výstup textu:
            if (isRtf)
                return this.RtfText.RtfText;
            else
                return this.TxtText.ToString();
        }
        private void _AddTitle(string text)
        {
            if (this.CurrentCoderIsRtf)
            {
                this.RtfText.Add(
                    RtfItem.NewFont("Arial CE"),
                    RtfItem.NewSize(11),
                    RtfItem.NewFontStyle(RtfFontStyle.Bold),
                    RtfItem.NewText(text),
                    RtfItem.NewFontStyle(RtfFontStyle.BoldEnd),
                    RtfItem.NewLine,
                    RtfItem.NewFont("Courier New CE"),
                    RtfItem.NewFontStyle(RtfFontStyle.Regular),
                    RtfItem.NewSize(10));
            }
            else
            {
                this.TxtText.AppendLine(text);
            }
        }
        private void _AddComment(string label, string value)
        {
            if (!String.IsNullOrEmpty(value))
                _AddComment(label + value);
        }
        private void _AddCommentBlock(string text)
        {
            if (this.CurrentCoderIsRtf)
            {
                if (!String.IsNullOrEmpty(text))
                {
                    _AddComment("/*  " + text + "  */");
                    this.RtfText.Add(RtfItem.NewLine);
                }
            }
            else
            {
                this.TxtText.AppendLine(text);
            }
        }
        private void _AddComment(string text)
        {
            if (this.CurrentCoderIsRtf)
            {
                this.RtfText.Add(RtfItem.NewFontStyle(RtfFontStyle.Italic),
                                RtfItem.NewHighlight(Color.FromArgb(194, 206, 220)),
                                RtfItem.NewText(text),
                                RtfItem.NewHighlight(RtfHighlightType.None),
                                RtfItem.NewFontStyle(RtfFontStyle.ItalicEnd),
                                RtfItem.NewLine);
            }
            else
            {
                this.TxtText.AppendLine(text);
            }
        }
        private void _AddSqlText(string text)
        {
            if (this.CurrentCoderIsRtf)
            {
                this.RtfText.Add(
                    RtfItem.NewFont("Courier New CE"),
                    RtfItem.NewFontStyle(RtfFontStyle.Regular));

                if (this.CurrentCoderMode == CoderMode.RtfSyntaxColoring)
                    this.RtfText.AddRange(_GetSqlSyntaxColouredItems(text));
                else
                    this.RtfText.Add(RtfItem.NewText(text));

                this.RtfText.Add(RtfItem.NewFontStyle(RtfFontStyle.Regular),
                    RtfItem.NewForeColor(Color.Black),
                    RtfItem.NewLine);
            }
            else
            {
                this.TxtText.AppendLine(text);
            }
        }
        /// <summary>
        /// Vrátí RTF text obsahující syntakticky zpracovaný vstupující holý text
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        private IEnumerable<RtfItem> _GetSqlSyntaxColouredItems(string text)
        {
            Parser.ParserSetting setting = Parser.ParserDefaultSetting.MsSql;

            List<Parser.ParserSegment> segments = Parser.Parser.ParseString(text, setting);
            if (segments == null || segments.Count == 0) return new List<RtfItem>();

            Parser.Lexer.Lexe(segments);

            return segments[0].RtfItems;
        }
        /// <summary>
        /// Vrátí trimovaný poslední řádek textu.
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        private string _GetLastSqlLine(string text)
        {
            if (String.IsNullOrEmpty(text)) return text;

            // Najdu poslední neprázdný řádek:
            string[] lines = text.Split('\r', '\n');
            string result = "";
            foreach (string line in lines)
            {
                string trimmed = line.Trim();
                if (trimmed.Length > 0)
                    result = trimmed;
            }
            if (String.IsNullOrEmpty(result)) return result;

            return result;
        }
        protected static int CompareISqlEvent(ISqlEventData a, ISqlEventData b)
        {
            if (a == null || b == null) return 0;

            int cmp = a.SqlPId.CompareTo(b.SqlPId);
            if (cmp != 0) return cmp;

            return a.Time.Begin.CompareTo(b.Time.Begin);
        }
        /// <summary>
        /// Objekt RTF generátoru pro kumulaci RTF textů
        /// </summary>
        protected RtfCoder RtfText;
        /// <summary>
        /// Objekt StringBuilderu pro kumulaci plain textů
        /// </summary>
        protected StringBuilder TxtText;
        /// <summary>
        /// Aktuální režim kompletace textu
        /// </summary>
        protected CoderMode CurrentCoderMode;
        /// <summary>
        /// true když se výstup dává do RTF  /  false = do TXT
        /// </summary>
        protected bool CurrentCoderIsRtf { get { return (this.CurrentCoderMode == CoderMode.RtfSyntaxColoring || this.CurrentCoderMode == CoderMode.RtfPlain); } }
        /// <summary>
        /// Režim kompletace textu
        /// </summary>
        protected enum CoderMode { None = 0, RtfSyntaxColoring, RtfPlain, OnlyText }
        #endregion
    }
    #endregion
    #region Zobrazení SQL příkazů jako Grid
    public class Func_SqlShowGrid : Func_SqlBase, IFunctionContextMenuItem
    {
        #region IPlugin Members
        /// <summary>
        /// Příznak, že Plugin je aktivní ve standardním běhu (tj. při běžném užiavtelském spuštění).
        /// Plně funkční pluginy tedy vrací true.
        /// Pluginy, které jsou ve vývoji, vrací false, takové jsou zobrazeny / spouštěny pouze v aplikaci, která běží v režimu Debug.
        /// </summary>
        bool IPlugin.Active { get { return true; } }
        #endregion
        #region IFunctionContextMenuItem Members
        bool IFunctionContextMenuItem.IsFunctionSuitableFor(FunctionContextMenuItemSuitableArgs args)
        {
            args.MenuItems.Add(new FunctionMenuItem(31, "Vypiš SQL příkazy jako Tabulku", true));
            return true;
        }
        bool IFunctionContextMenuItem.IsMenuItemEnabledFor(FunctionContextMenuItemRunArgs args)
        {
            List<EventSqlItem> sqlItems = this.GetSelectedSqlEvents(args, 1);
            return (sqlItems != null && sqlItems.Count > 0);
        }
        void IFunctionContextMenuItem.Run(FunctionContextMenuItemRunArgs args)
        {
            this.RunShowSql(args);
        }
        #endregion
        #region Práce s SQL dotazy
        /// <summary>
        /// Načte a zobrazí SQL příkazy
        /// </summary>
        /// <param name="args"></param>
        protected void RunShowSql(FunctionContextMenuItemRunArgs args)
        {
            List<EventSqlItem> sqlItems = this.GetSelectedSqlEvents(args);
            int cnt = sqlItems.Count;
            if (cnt == 0)
            {   // K tomu by nemělo dojít, protože pokud nejsou vybrané SQL záznamy, pak je položka menu Disabled.
                Dialog.ShowInfo("Mezi vybranými záznamy není žádný SQL příkaz.");
                return;
            }

            // Z položek typu EventSqlItem udělám zdejší položky odpovídající interface IMasterDetails, do nich naplním jen některé údaje, a zobrazím v Gridu:
            if (sqlItems.Count > 1)
                sqlItems.Sort((a, b) => b.Time.Seconds.CompareTo(a.Time.Seconds));          // Setřídím podle doby trvání sestupně

            List<IMasterDetails> nrsItems = new List<IMasterDetails>();
            foreach (EventSqlItem sqlItem in sqlItems)
                nrsItems.Add(new SqlShowDetail(sqlItem));
            this.RunShowDetail(nrsItems, EventDetailViewMode.Grid);
        }
        /// <summary>
        /// Pomocná třída, která reprezentuje jednu položku SQL příkazu při jeho zobrazení v tabulce.
        /// Obsahuje méně detailů, než nabízí třída EventSqlItem, aby byl Grid čitelnější.
        /// </summary>
        protected class SqlShowDetail : IMasterDetails
        {
            #region Konstrukce
            internal SqlShowDetail(EventSqlItem sqlItem)
            {
                this.Details = new List<MasterDetailItem>();
                this.Fill(sqlItem);
            }
            internal string MasterName;
            internal List<MasterDetailItem> Details;
            #endregion
            #region Naplnění daty
            /// <summary>
            /// Naplnění daty
            /// </summary>
            /// <param name="sqlItem"></param>
            private void Fill(EventSqlItem sqlItem)
            {
                // Name:
                this.MasterName = sqlItem.FirstSqlWord + "  " + sqlItem.Time.Seconds.ToString("# ##0.000") + " sec [" + sqlItem.Login + "]";        // Name: "SELECT  2.54 sec [daj]"

                // Grid i Detail:
                this.Details.Add(new MasterDetailItem("Duration [sec]", sqlItem.Time.Seconds, new System.Drawing.Size(50, 19), true, true));
                this.Details.Add(new MasterDetailItem("SQL command", sqlItem.SqlCommand, new System.Drawing.Size(450, 19), true, true));
                this.Details.Add(new MasterDetailItem("Time", sqlItem.Time.StringLong, new System.Drawing.Size(160, 19), true, true));
                this.Details.Add(new MasterDetailItem("SPID", sqlItem.SqlPId, new System.Drawing.Size(50, 19), true, true));
                this.Details.Add(new MasterDetailItem("Login", sqlItem.Login, new System.Drawing.Size(100, 19), true, true));
                this.Details.Add(new MasterDetailItem("Params", sqlItem.SqlParameters, new System.Drawing.Size(150, 19), true, true));
                this.Details.Add(new MasterDetailItem("Result RowCount", sqlItem.ResultRowCount, new System.Drawing.Size(70, 19), true, true));
                this.Details.Add(new MasterDetailItem("Result scalar", sqlItem.ResultScalar, new System.Drawing.Size(70, 19), true, true));

                // Jen do Detailu:
                this.Details.Add(new MasterDetailItem("Trace", sqlItem.Trace.FileDirName, new System.Drawing.Size(100, 19), true, false));
                this.Details.Add(new MasterDetailItem("LineNumber", sqlItem.LineNumber, new System.Drawing.Size(100, 19), true, false));
                this.Details.Add(new MasterDetailItem("SecureId", sqlItem.SecureId, new System.Drawing.Size(100, 19), true, false));
                this.Details.Add(new MasterDetailItem("ThreadId", sqlItem.ThreadId, new System.Drawing.Size(100, 19), true, false));
                this.Details.Add(new MasterDetailItem("Keyword", sqlItem.Keyword, new System.Drawing.Size(100, 19), true, false));
                this.Details.Add(new MasterDetailItem("Method", sqlItem.Method, new System.Drawing.Size(100, 19), true, false));
                this.Details.Add(new MasterDetailItem("Stack", sqlItem.Stack, new System.Drawing.Size(100, 65), true, false));
            }
            #endregion
            #region IMasterDetails Members
            string IMasterDetails.MasterName { get { return this.MasterName; } }
            System.Drawing.Color IMasterDetails.DetailBackColor { get { return System.Drawing.Color.Empty; } }
            IEnumerable<MasterDetailItem> IMasterDetails.Details { get { return this.Details; } }
            #endregion
        }
        #endregion
    }
    #endregion
}
